package misc

import (
	"encoding/binary"
	"jvmgo/native"
	"jvmgo/rtda"
)

func init() {
	_unsafe(allocateMemory, "allocateMemory", "(J)J")
	_unsafe(reallocateMemory, "reallocateMemory", "(JJ)J")
	_unsafe(freeMemory, "freeMemory", "(J)V")
	_unsafe(addressSize, "addressSize", "()I")
	_unsafe(getByte, "getByte", "(J)B")
	_unsafe(putLong, "putLong", "(JJ)V")
}

func putLong(frame *rtda.Frame) {
	vars := frame.LocalVars()
	address := vars.GetLong(1)
	value := vars.GetLong(3)
	mem := memoryAt(address)
	PutInt64(mem, value)
}

var _bigEndian = binary.BigEndian

func PutInt64(mem []byte, value int64) {
	_bigEndian.PutUint64(mem, uint64(value))
}

func getByte(frame *rtda.Frame) {
	stack, mem := _get(frame)
	stack.PushInt(int32(Int8(mem)))
}

func Int8(mem []byte) int8 {
	return int8(mem[0])
}

func _get(frame *rtda.Frame) (*rtda.OperandStack, []byte) {
	vars := frame.LocalVars()
	address := vars.GetLong(1)
	stack := frame.OperandStack()
	mem := memoryAt(address)
	return stack, mem

}

func addressSize(frame *rtda.Frame) {
	frame.OperandStack().PushInt(8)
}

func freeMemory(frame *rtda.Frame) {
	vars := frame.LocalVars()
	address := vars.GetLong(1)
	free(address)
}

func free(address int64) {
	if _, ok := _allocated[address]; ok {
		delete(_allocated, address)
	} else {
		panic("Memory was not allocated!")
	}
}
func reallocateMemory(frame *rtda.Frame) {
	vars := frame.LocalVars()
	address := vars.GetLong(1)
	bytes := vars.GetLong(3)
	newAddress := reallocate(address, bytes)
	frame.OperandStack().PushLong(newAddress)
}

func reallocate(address int64, size int64) int64 {
	if size == 0 {
		return 0
	} else if address == 0 {
		return allocate(size)
	} else {
		mem := memoryAt(address)
		if len(mem) >= int(size) {
			return address
		} else {
			delete(_allocated, address)
			newAddress := allocate(size)
			newMem := memoryAt(newAddress)
			copy(newMem, mem)
			return newAddress
		}
	}
}

func memoryAt(address int64) []byte {
	for startAddress, mem := range _allocated {
		endAddress := startAddress + int64(len(mem))
		if address >= startAddress && address < endAddress {
			offset := address - startAddress
			return mem[offset:]
		}
	}
	panic("invalid address")
	return nil
}

func allocateMemory(frame *rtda.Frame) {
	vars := frame.LocalVars()
	bytes := vars.GetLong(1)

	address := allocate(bytes)
	stack := frame.OperandStack()
	stack.PushLong(address)
}

func _unsafe(method func(frame *rtda.Frame), name, descriptor string) {
	native.Register("sun/misc/Unsafe", name, descriptor, method)
}

var _nextAddress = int64(64)
var _allocated = map[int64][]byte{}

func allocate(size int64) (address int64) {
	mem := make([]byte, size)
	address = _nextAddress
	_allocated[address] = mem
	_nextAddress += size
	return
}
